#import "Alexandra.h"
#import "KillFile.h"
#import "descriptors.h"
#import "Article.h"
#import "NNTP.h"
#import "ArticleSet.h"
#import "ArticleSetMatrix.h"
#import "MainWindowControl.h"
#import "NntpPanelControl.h"
#import <misckit/MiscAppDefaults.h>
#import "plain-subject.h"
#import "MatrixScroller.h"

#define MODE_AUTHOR 	1
#define MODE_THREAD 	0
#define MODE_ASK		2
#define MODE_NOTHING	-1

@implementation KillFile

- setPath
{
   const char *home=NXHomeDirectory();
   const char *servername=[nntpServer serverName];

   path=(char *)malloc((strlen(home)+strlen(servername)+25)*sizeof(char));
   strcpy(path,home);
   strcat(path,"/Library/Alexandra");
   mkdir(path,448);
   strcat(path,"/");
   strcat(path,servername);
   mkdir(path,448);
   strcat(path,"/");

   return self;  
}

- init
{
   newsgroupTable=[[HashTable alloc] initKeyDesc:"*" valueDesc:"@"];
   authorTable=NULL;
   return self;
}

- free
{
   [self saveAndFree];
   
   if(path!=NULL)
      free(path);

   return [super free];
}

- getAuthorTable
{
   char buf[255],buf2[255];
   FILE *fd;
   char *fullPath;
   char tableFile[]="KILLAUTHORS";

   if(authorTable!=NULL)
      return authorTable;
   fullPath=(char *)malloc(strlen(path)+strlen(tableFile)+2);
   strcpy(fullPath,path);
   strcat(fullPath,tableFile);
   authorTable=[[HashTable alloc] initKeyDesc:"*" valueDesc:"i"];

   fd=fopen(fullPath,"r");   
   if(fd!=NULL){
      while(fgets(buf,255,fd),((buf[0]!='.')&&(buf[0]!='\0'))){
         if(buf[0]!='A')
            continue;
			*buf2='\0';
         sscanf(buf,"A %[^\n]\n",buf2);
			if(strlen(buf2)>2)
            [authorTable insertKey:(const void *)NXCopyStringBuffer(buf2) value:(void *)1];
      }
   fclose(fd);
   }

   free(fullPath);

   return authorTable; 
}

- getThreadTableFor:(const char *)newsgroup
{
   id groupTable=[newsgroupTable valueForKey:newsgroup];
   char *fullPath;
   FILE *fd;
   char buf[255],buf2[255];

   long *s;
   long seconds;
   struct timeval tp;
   struct timezone tzp;
   long dist;

   if(newsgroup==NULL){
	   NXLogError("KillFile: Newsgroup with NULL pointer as name");
		return groupTable;
	}
	
   gettimeofday(&tp,&tzp);
   dist=[NXApp defaultIntValue:"KillfileExpire"];
   if(dist==0)
      dist=30;
   dist=dist*24*60*60;

   if(groupTable!=nil)
      return groupTable;
   groupTable=[[HashTable alloc] initKeyDesc:"*" valueDesc:"!"];
   [newsgroupTable insertKey:NXCopyStringBuffer(newsgroup) value:groupTable];

   fullPath=(char *)malloc(strlen(path)+strlen(newsgroup)+2);
   strcpy(fullPath,path);
   strcat(fullPath,newsgroup);

   fd=fopen(fullPath,"r");   
   if(fd!=NULL){
      while(fgets(buf,255,fd),((buf[0]!='.')&&(buf[0]!='\0'))){
         if(buf[0]!='S')
            continue;
			*buf2='\0';
         sscanf(buf,"S %[^\t]\t%ld",buf2,&seconds);
         if(strlen(buf2)>0 && tp.tv_sec-seconds<dist){
            s=(long *)malloc(sizeof(long));
            *s=seconds;
            [groupTable insertKey:(const void *)NXCopyStringBuffer(buf2) value:(void *)s];
         }
      }
   fclose(fd);
   }
   free(fullPath);

   return groupTable;
}

- killUnkillAuthor:(BOOL)doKill author:(const char *)author
{
   id aTable=[self getAuthorTable];

   if((author==NULL)||(author[0]=='\0')) return nil;
   if(doKill==TRUE){
      if(![aTable isKey:author]){
         char *name=NXCopyStringBuffer(author);
         [aTable insertKey:(const void *)name value:(void *)1];
      }
   }
   else
      [aTable removeKey:author];

   return self;
}

- killUnkillThread:(BOOL)doKill :(char *)subject newsgroup:(id)group
{
   char *val;
   id subjectTable=[self getThreadTableFor:[group stringValue]];
   struct timeval tp;
   struct timezone tzp;
   BOOL dummy;

   gettimeofday(&tp,&tzp);

   if((subject==NULL)||(subject[0]=='\0')) return nil;
   val=plain_subject(subject,&dummy);
   if((val==NULL)||(val[0]=='\0')) return nil;
   if((doKill==TRUE)&&([subjectTable isKey:val]==FALSE)){
      long *s=(long *)malloc(sizeof(long));
      *s=tp.tv_sec;
      [subjectTable insertKey:(const void *)NXCopyStringBuffer(val) value:(void *)s];
   }
   else{
      [subjectTable removeKey:val];
      free(val);
   }

   return self;
}

- filterArticles:(id)newsgroup andReloadMatrix:(BOOL)reload
{
   int i,j;
   id aList=[newsgroup articleList];
   id subjectTable=[self getThreadTableFor:[newsgroup stringValue]];
   id aTable=[self getAuthorTable];
   int num_unread_killed=0;
   struct timeval tp;
   struct timezone tzp;
   BOOL subjectFits,authorFits;
   BOOL dummy;
   int rememberNextPos=0;
	id nextCell=nil;
	id selectedCell=[[theArticleSet theMatrix] selectedCell];

   gettimeofday(&tp,&tzp);

   j=[aList count];
   for(i=0;i<j;i++){
      id anArticle=[aList objectAt:i];
      const char *subject,*lsubject;
      const char *name;
		headerDesc *h;
		
      if([anArticle isKilled])
         continue;
		
		h=[anArticle header];
		if(!h){
			NXLogError("KillFile.m: article with NULL pointer header");
			continue;
		}
		
		lsubject=h->fieldBody[SUBJECT];
		if(!lsubject){
		   NXLogError("KillFile: Article with NULL pointer as subject");
			continue;
		}
		
      subject=plain_subject((char *)lsubject,&dummy);
      name=[anArticle realName];
      subjectFits=((subject!=NULL)&&([subjectTable isKey:subject]));
      authorFits=((name!=NULL)&&([aTable isKey:name]));
      if( subjectFits || authorFits){
         if([anArticle isRead]==FALSE)
            num_unread_killed++;
         [anArticle kill];
         if(subjectFits)
            *(long *)[subjectTable valueForKey:subject]=tp.tv_sec;
			if((rememberNextPos==0)&&(selectedCell==anArticle))
			   rememberNextPos=1;
      }
	   else
		   if((rememberNextPos==1)&&([anArticle isTaged])){
			   rememberNextPos=2;
				nextCell=anArticle;
			}
			   
   }
   if(reload==TRUE){
      id matrix=[theArticleSet theMatrix];
      [matrix reloadMatrix];
      [matrix display];
      [theArticleSet sync];
      if(num_unread_killed>0){
         [newsgroup incNumberUnreadArticles:-1*num_unread_killed];
         [[theNGSet theMatrix] display];
      }
		if(nextCell!=nil){
		   int row,col;
			[matrix getRow:&row andCol:&col ofCell:nextCell];
		   [matrix selectCellAt:row :col];
         [theArticleSet selectArticle:self];
         [matrix scrollCellToVisible:row upperOffset:1.5 lowerOffset:1.5];
      }
   }

   return self;
}

- saveAndFree
{
   FILE *fd;
   char *fullPath;
   char tableFile[]="KILLAUTHORS";
   BOOL success=TRUE;

   if(authorTable!=nil){
      fullPath=(char *)malloc(strlen(path)+strlen(tableFile)+2);
      strcpy(fullPath,path);
      strcat(fullPath,tableFile);

      if([authorTable count]>0){
         fd=fopen(fullPath,"w");
         if(fd==NULL)
            success=FALSE;
         else{
            char *name; 
            int *value; 
            NXHashState state=[authorTable initState]; 
            while([authorTable nextState:&state key:(const void **)&name value:(void **)&value]){
               fprintf(fd,"A %s\n",name);
               free(name);
            }
            fprintf(fd,".\n");
            fclose(fd);
         }
         free(fullPath);
      }
      else
         remove(fullPath);
      free(fullPath);
      [authorTable free];
   }

   if([newsgroupTable count]>0){
      char *gname;
      id groupTable; 
      NXHashState ngstate=[newsgroupTable initState]; 
      while([newsgroupTable nextState:&ngstate key:(const void **)&gname value:(void **)&groupTable]){
         fullPath=(char *)malloc(strlen(path)+strlen(gname)+2);
         strcpy(fullPath,path);
         strcat(fullPath,gname);
 
         if([groupTable count]>0){
            fd=fopen(fullPath,"w");
            if(fd==NULL)
               success=FALSE;
            else{
               char *subject; 
               long *value; 
               NXHashState gstate=[groupTable initState]; 
               while([groupTable nextState:&gstate key:(const void **)&subject value:(void **)&value]){
                  fprintf(fd,"S %s\t%ld\n",subject,*value);
                  free(subject);
                  free(value);
               }
               fprintf(fd,".\n");
               fclose(fd);
            }
         }
         else
            remove(fullPath);
         free(fullPath);
         [groupTable free];
      }
   }

   [newsgroupTable free];
   if(success==FALSE)
      NXLogError("Can't save killfile.\n");

   return self;
}

- killAuthor:sender
{
   [self killSelectionMode:MODE_AUTHOR];
   return self;
}

- killThread:sender
{
	
   if([theArticleSet currentSelection]==nil){
      NXRunAlertPanel("ALEXANDRA","No article selected.",NULL,NULL,NULL);
      return nil;
   }
   if(![NXApp defaultBoolValue:"DoNotConfirmAKill"])
      if(NXRunAlertPanel("ALEXANDRA","Do you really want to kill this thread?",NULL,"Cancel",NULL)==NX_ALERTALTERNATE)
         return self;

   [self killSelectionMode:MODE_THREAD];
	
   return self;
}


#define ALERT_KILLWHAT NXLocalString("Do you want to kill all articles of this thread or all articles written by this author?",NULL,"Frage was ge-kill-t werden soll.")

#define ALERT_NOARTICLE NXLocalString("You must select an Article first.",NULL,"Alert Panel falls Kill aufgerufen wird ohne daû ein Artikel selektiert ist.")

#define STRG_CANCEL NXLocalString("Cancel",NULL,"In Alert-Panels")
#define STRG_AUTHOR NXLocalString("Author",NULL,"In Kill-Alert-Panel")
#define STRG_THREAD NXLocalString("Thread",NULL,"In Kill-Alert-Panel")


- kill:sender;
	{
	int killMode,ret;
	
	if([theArticleSet currentSelection]==nil)
		{
		NXRunAlertPanel("ALEXANDRA",ALERT_NOARTICLE,STRG_CANCEL,NULL,NULL);
		return nil;
		}
	killMode=[NXApp defaultIntValue:DEFAULT_KILL_BEHAVIOUR];
	if(killMode==MODE_ASK)
		{
		ret=NXRunAlertPanel("ALEXANDRA", ALERT_KILLWHAT, STRG_THREAD,
					STRG_AUTHOR, STRG_CANCEL);
		if(ret==NX_ALERTDEFAULT)
			killMode=MODE_THREAD;
		else if(ret==NX_ALERTALTERNATE)
			killMode=MODE_AUTHOR;
		else
			killMode=MODE_NOTHING;
		}
	if(killMode!=MODE_NOTHING)
		[self killSelectionMode:killMode];
	return self;
	}


- killSelectionMode:(int)mode
{
   id currentNewsgroup,currentArticle;
   char *subject;
	
   if((currentNewsgroup=[theNGSet currentSelection])==nil)
      return nil;
   if((currentArticle=[theArticleSet currentSelection])==nil)
      return nil;

   if(mode==MODE_THREAD){
      subject=[currentArticle header]->fieldBody[SUBJECT];
      
      if((subject==NULL)||(subject[0]=='\0')) 
         return nil;
      [self killUnkillThread:YES :subject newsgroup:currentNewsgroup];
      [self filterArticles:currentNewsgroup andReloadMatrix:YES];
   }
   else{
      const char *name=[currentArticle realName];
      if((name==NULL)||(name[0]=='\0'))
         return nil;
      [self killUnkillAuthor:YES author:name];
      [self filterArticles:currentNewsgroup andReloadMatrix:YES];
   }

   return self;
}

@end
